/*
 * hex2bin.c
 *
 *  Created on: Nov 7, 2018
 *      Author: ax
 *
 * Purpose :
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define msg printf
#define DEF_APP_FOOSET  0x20000//12KB, user defined inut value in argv[4] should match with boot loader's requirement.
#define BL_OFFSET       0x1000//4KB
#define DIR             //"./"

typedef enum {
    preamble = ':',
    separator = '\n',
    dataType = '0',
    eofType = '1',
    rebaseType = '4'
} ihexConst;

#define IHEX_SZ_MAX     0x20
#define BYTE_HEX_SZ     2
#define DB_BYTE_SZ      2
typedef uint8_t Hex;
typedef struct {
    Hex preamble;
    Hex size[BYTE_HEX_SZ];
    Hex offset[DB_BYTE_SZ * BYTE_HEX_SZ];
    Hex type[BYTE_HEX_SZ];
    Hex payload[IHEX_SZ_MAX * DB_BYTE_SZ];
    Hex checksum[BYTE_HEX_SZ];
    Hex separator;
    Hex rsvd[8];
} IHexRecord;

#define hex2b(hex)      ((hex <= '9') ? (hex & 0xf) : (hex - 'A' + 10))

static __inline uint8_t hex2byte(const Hex* hex) {
    register uint8_t h = *hex++;
    register uint8_t b = hex2b(h) << 4;
    h = *hex;
    b |= hex2b(h);
    return b;
}

static __inline uint16_t hex2db(register const Hex* hex, char be) {
    register uint16_t db;
    if (be) {
        db = hex2byte(hex) << 8;
        db |= hex2byte(hex + BYTE_HEX_SZ);
    } else {
        db = hex2byte(hex);
        db |= hex2byte(hex + BYTE_HEX_SZ) << 8;
    }
    return db;
}

static __inline void hex2nBytes(register const Hex* hex, register int16_t n, register uint8_t* bytes) {
    while(--n >= 0) {
        *bytes++ = hex2byte(hex);
        hex += BYTE_HEX_SZ;
    }
}


static void b2s(uint8_t* buf, int8_t sz) {
    if (sz == 0) return;

    while (--sz >= 0) {
        *(buf + ((sz << 1) + 1)) = *(buf + sz);
        *(buf + (sz << 1)) = 0xff;
    }
}

#define USE_APP (mask&1)
#define USE_UP  (mask&2)
#define BE      (mask&4)
#define EXT_BIT (mask&8)
int main(int argc, char* argv[]) {
    /* help info. */
    if (argc == 1) {
        printf("usage:\n"
               "\thex2bin  image.bin  mask(ext-16 | BE | use updater.hex | app.hex)  65536(|...align)\n"
               "\tconvert .hex file of bootloader, updater & app(if they are used) to image.bin with assigned endian & ext.\n");
        return -1;
    }


    /* action check. */
    unsigned mask = (unsigned)(atol(argv[2]));
    char be = BE;
    char ext = EXT_BIT;
    unsigned appOffset =  USE_APP
                        ? ((argc == 4) ? (unsigned)(atol(argv[3])) : DEF_APP_FOOSET)
                        : 0;
    if (ext) appOffset <<= 1;
    printf("convert bootloader.hex updater.hex(%c) & app.hex(%c, with %d offset) to %s with %s endian & %s bit extension.\n",
           USE_UP ? 'Y' : 'N', USE_APP ? 'Y' : 'N', appOffset, argv[1], be ? "big" : "little", ext ? "8-16" : "no");


    /* boot loader */

    printf("convert boot loader...\n");
    char* file = DIR"bootloader.hex";
    FILE* ifp = fopen(file, "r");
    if (NULL == ifp) {
        perror(NULL);
        msg("\thex file %s not exist!\n", file);
        return -2;
    }

    file = argv[1];
    FILE* ofp = fopen(file, "w");
    if (NULL == ofp) {
        msg("\tcreat output file %s failed!\n", file);
        perror(NULL);
        return -3;
    }

    IHexRecord  ihex;
    uint32_t    rbase = 0;
    uint16_t    roff;
    int8_t      rsz;
    char        rt;

    uint32_t    fsz = 0;//file size

    uint8_t dummy[IHEX_SZ_MAX * 2];
    do {
        (void)fgets((char*)&ihex, sizeof(IHexRecord), ifp);
        if (preamble != ihex.preamble) {
            printf("invalid preamble %x-%c in file.\n", rt, rt);
            return -4;
        }
        rt = ihex.type[1];

        if (rt == dataType) {
            rsz = hex2byte(&ihex.size[0]);
            if (rsz > IHEX_SZ_MAX) {
                printf("invalid record size: %x.\n", rsz);
                return -5;
            }
            roff = hex2db(&ihex.offset[0], be);
            hex2nBytes(&ihex.payload[0], rsz, &dummy[0]);
            if (ext)  {
                b2s(&dummy[0], rsz);
                rsz <<= 1;
            }
            fwrite(&dummy, rsz, 1, ofp);
            fsz += rsz;
       } else if(rt == rebaseType) {
            rbase = (uint32_t)(hex2db(&ihex.payload[0], be) << 16);
            printf("\tebase @%x:", rbase);
        }

        msg(".");fflush(stdout);
    } while (rt != eofType);
    msg("\n\t%d size for bootloader.\n", fsz);

    unsigned rest = (ext ? (BL_OFFSET << 1) : BL_OFFSET) - fsz;
    memset(&dummy[0], 0xff, sizeof(dummy));
    fwrite(&dummy, rest % sizeof(dummy), 1, ofp);
    fsz += (rest % sizeof(dummy));

    rest &= ~(sizeof(dummy) - 1);
    fsz += rest;
    for (rest /= sizeof(dummy); rest != 0; rest--)
        fwrite(&dummy, sizeof(dummy), 1, ofp);

    fclose(ifp);


    /* updater & app to bin */
    char app = 0;
    char up = 1;

    H2B:
    if (up) {
        app = 1; up = 0;
        if (USE_UP)
            file = DIR"updater.hex";
        else
            goto FILL_HOLE_TO_APP;
    } else if (app & USE_APP) {
        file = DIR"app.hex";
        app = 0;
    } else {
        printf("curing finished!\n");
        fclose(ofp);
        return 0;
    }

    printf("curing %s @%x\n", file, fsz);
    unsigned st = fsz;

    ifp = fopen(file, "r");
    if (NULL == ifp) {
        perror(NULL);
        msg("\tfile %s not exist!\n", file);
        return -2;
    }

    do {//each record line
        (void)fgets((char*)&ihex, sizeof(IHexRecord), ifp);
        if (preamble != ihex.preamble) {
            printf("\tinvalid preamble %x-%c in file.\n", rt, rt);
            return -6;
        }

        rt = ihex.type[1];
        if (eofType == rt) {
            break; }
        else if (rebaseType == rt) {
            continue; }
        else if (dataType != rt) {
            printf("invalid data type %c-0x%x.\n", rt, rt);
            return -7;
        }

        rsz = hex2byte(&ihex.size[0]);
        if (rsz > IHEX_SZ_MAX) {
            printf("invalid record size: %x.\n", rsz);
            return -8;
        } else if (0 == rsz) {
            printf("\t end of image.\n");
            break; }

        hex2nBytes(&ihex.payload[0], rsz, &dummy[0]);
        if (ext)  {
            b2s(&dummy[0], rsz);
            rsz <<= 1;
        }
        fwrite(&dummy[0], rsz, 1, ofp);
        fsz += rsz;
    } while (1);

    FILL_HOLE_TO_APP:
    if (appOffset > fsz) {
        rest = appOffset - fsz;
        memset(&dummy[0], 0xff, sizeof(dummy));
        fwrite(&dummy, rest % sizeof(dummy), 1, ofp);
        rest &= ~(sizeof(dummy) - 1);
        for (rest /= sizeof(dummy); rest > 0; rest--)
            fwrite(&dummy, sizeof(dummy), 1, ofp);

        fsz = appOffset;
    }

    fclose(ifp);
    printf("\tfinished with %d size.\n", fsz - st);

    goto H2B;
}



